// ---------------------------------------------------------------------------------------------------------------------
//   Copyright (c) SRT Solutions 2009. All rights reserved.
// ---------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using NUnit.Framework;

using SRTSolutions.Elevate;

namespace SRTSolutions.Elevate.Guide._001_Sequences._002_WorkingWithSequences
{
#if NET_3_5
#else
    using Tuple=System.Tuple;
#endif
    public class _002_OperationsOnMultipleSequences
    {
        [Test]
        public void Compare()
        {
            //given two sequences
            var one = 1.Through(5);
            var two = 1.Through(5); ;

            //we can compare them element by element using compare
            //the parameterless overload uses Compare<T>.Default
            int result = one.Compare(two);

            Assert.AreEqual(0, result);
        }

        [Test]
        public void CompareFirstHasMore()
        {
            //given two sequences containing the same values, but one has more than the other
            var one = Seq.Build<int>(1, 2);
            var two = Seq.Build<int>(1);

            //compare will return 1 if the first sequence contains more elements
            Assert.AreEqual(1, one.Compare(two));
        }

        [Test]
        public void CompareSecondHasMore()
        {
            //given two sequences containing the same values, but one has more than the other
            var one = Seq.Build<int>(1, 2);
            var two = Seq.Build<int>(1, 2, 3);

            //compare will return -1 if the second sequence contains more elements
            Assert.AreEqual(-1, one.Compare(two));
        }

        [Test]
        public void CompareWithSelector()
        {
            //given two sequences
            var one = 1.Through(5).Select(value => value.ToString());
            var two = 1.Through(5);

            //we can compare them element by element as we did above,
            //but instead of using the default comparer's
            //we'll provide our own. We can also compare sequences
            //of two different types
            int result = one.Compare(two, (first, second) => int.Parse(first) - second);

            Assert.AreEqual(0, result);
        }

        [Test]
        public void ForEach2()
        {
            //given two sequences
            var one = 0.Through(3);
            var two = 4.Through(7);

            //we can walk the sequences item by item and perform an action on the pairs of values.
            using (var stream = new MemoryStream())
            {
                var writer = new StreamWriter(stream);

                one.ForEach2(two, (oneValue, twoValue) => writer.WriteLine(string.Format("one: {0} two: {1}", oneValue, twoValue)));

                //verify results
                writer.Flush();
                stream.Position = 0;

                var expected = string.Join(System.Environment.NewLine, Seq.Build("one: 0 two: 4", "one: 1 two: 5", "one: 2 two: 6", "one: 3 two: 7").ToArray());
                expected += System.Environment.NewLine;
                Assert.AreEqual(expected, new StreamReader(stream).ReadToEnd());
            }
        }

        [Test]
        public void ForEach2WillOnlyContinueUntilOneSequenceRunsOut()
        {
            //given two sequences of unequal length
            var one = 0.Through(3);
            var two = Seq.Build("alpha", "beta");

            //we can foreach them
            var lastOne = -1;
            var lastTwo = "";
            one.ForEach2(two, (oneValue, twoValue) => { lastOne = oneValue; lastTwo = twoValue; });

            //but the foreach will stop when one sequence runs out
            Assert.AreEqual(1, lastOne);
            Assert.AreEqual("beta", lastTwo);
        }

        [Test]
        public void Zip()
        {
            //given two sequences
            var one = 0.Through(5);
            var two = 2.Through(12, 2);

            //we use Zip to walk the sequences in parallel and apply a selector function
            var result = one.Zip(two, (elementFromFirst, elementFromSecond) => elementFromFirst * elementFromSecond);

            var expected = Seq.Build(0, 4, 12, 24, 40, 60);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void ZipUneven()
        {
            //given two sequences of uneven length
            var one = 0.Through(3);
            var two = Seq.Build(0, 3);

            //Zip will only take elements as long as both sequences have not run out
            var result = one.Zip(two, (elementFromFirst, elementFromSecond) => elementFromFirst * elementFromSecond);

            var expected = Seq.Build(0, 3);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void ZipToTuples()
        {
            //given two sequences
            var one = 0.Through(3);
            var two = 4.Through(6);

            //we can combine the sequences into a sequence of tuples
            var result = one.Zip(two);

            var expected = new[] { Tuple.Create(0, 4), 
                                   Tuple.Create(1, 5),
                                   Tuple.Create(2, 6) };
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void ZipToTuplesUneven()
        {
            //given two sequences of uneven length
            var one = 0.Through(3);
            var two = 4.Through(5);

            //Zip will only combine into tuples until one sequence runs out
            var result = one.Zip(two);

            var expected = new[] { Tuple.Create(0, 4), Tuple.Create(1, 5) };
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void MergeTwoSortedSequences()
        {
            var sequence1 = new int[] { 1, 3, 5, 7 };
            var sequence2 = new int[] { 2, 4, 6, 8 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);

        }
        [Test]
        public void MergeTwoSortedSequencesWhenFirstIsLonger()
        {
            var sequence1 = new int[] { 1, 3, 5, 7, 9, 11, 13, 15, 17 };
            var sequence2 = new int[] { 2, 4, 6, 8 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);

        }
        [Test]
        public void MergeTwoSortedSequencesWhenSecondIsLonger()
        {
            var sequence1 = new int[] { 1, 3, 5, 7 };
            var sequence2 = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);
        }

        #region Unit Tests

        [Test]
        public void CompareNegative()
        {
            var one = 1.Through(5);
            var two = Seq.Build(1, 2, 3, 684, 5);
            Assert.AreEqual(-1, one.Compare(two));
        }

        [Test]
        public void ComparePositive()
        {
            var one = 1.Through(5);
            var two = Seq.Build(1, 2, -1, 4, 5);
            Assert.AreEqual(1, one.Compare(two));
        }

        [Test]
        public void CompareThisException()
        {
            var two = 0.Through(2);
            Assert.Throws<ArgumentNullException>(() => ((IEnumerable<int>)null).Compare(two));
        }

        [Test]
        public void CompareArgumentException()
        {
            var one = 0.Through(2);
            Assert.Throws<ArgumentNullException>(() => one.Compare(null));
        }

        [Test]
        public void CompareEmpty()
        {
            var one = Seq.Build<int>();
            var two = Seq.Build<int>();

            Assert.AreEqual(0, one.Compare(two));
        }

        [Test]
        public void CompareWithComparerFirstHasMore()
        {
            //given two sequences containing the same values, but one has more than the other
            var one = Seq.Build<int>(1, 2);
            var two = Seq.Build<int>(1);

            //compare will return 1 if the first sequence contains more elements
            Assert.AreEqual(1, one.Compare(two, (x, y) => x - y));
        }

        [Test]
        public void CompareWithComparerSecondHasMore()
        {
            //given two sequences containing the same values, but one has more than the other
            var one = Seq.Build<int>(1, 2);
            var two = Seq.Build<int>(1, 2, 3);

            //compare will return -1 if the second sequence contains more elements
            Assert.AreEqual(-1, one.Compare(two, (x, y) => x - y));
        }

        [Test]
        public void CompareWithComparerNegative()
        {
            var one = 1.Through(5);
            var two = Seq.Build(1, 2, 3, 684, 5);
            Assert.Less(one.Compare(two, (x, y) => x - y), 0);
        }

        [Test]
        public void CompareWithComparerPositive()
        {
            var one = 1.Through(5);
            var two = Seq.Build(1, 2, -1, 4, 5);
            Assert.Greater(one.Compare(two, (x, y) => x - y), 0);
        }

        [Test]
        public void CompareWithComparerThisException()
        {
            var two = 0.Through(2);
            Assert.Throws<ArgumentNullException>(() => ((IEnumerable<int>)null).Compare(two, (x, y) => x - y));
        }

        [Test]
        public void CompareWithComparerArgumentExceptionOnSequence()
        {
            var one = 0.Through(2);
            Assert.Throws<ArgumentNullException>(() => one.Compare<int, int>(null, (x, y) => x - y));
        }

        [Test]
        public void ComparerWithComparerArgumentExceptionOnComparer()
        {
            var one = 0.Through(2);
            var two = 0.Through(2);
            Assert.Throws<ArgumentNullException>(() => one.Compare<int, int>(two, null));
        }

        [Test]
        public void CompareWithComparerEmpty()
        {
            var one = Seq.Build<int>();
            var two = Seq.Build<int>();

            Assert.AreEqual(0, one.Compare(two, (x, y) => x - y));
        }

        [Test]
        public void ForEach2ThisException()
        {
            var two = 0.Through(3);
            Assert.Throws<ArgumentNullException>(() => ((IEnumerable<int>)null).ForEach2(two, (valueOne, valueTwo) => valueOne++));
        }

        [Test]
        public void ForEach2SecondException()
        {
            var one = 0.Through(3);
            Assert.Throws<ArgumentNullException>(() => one.ForEach2<int, int>(null, (valueOne, valueTwo) => valueOne++));
        }

        [Test]
        public void ForEach2ActionException()
        {
            var one = 0.Through(3);
            var two = 0.Through(3);
            Assert.Throws<ArgumentNullException>(() => one.ForEach2<int, int>(two, null));
        }

        [Test]
        public void ForEach2FirstRunningOut()
        {
            var one = Seq.Build(0);
            var two = Seq.Build("alpha", "beta");

            //we can foreach them
            var lastOne = -1;
            var lastTwo = "";
            one.ForEach2(two, (oneValue, twoValue) => { lastOne = oneValue; lastTwo = twoValue; });

            //but the foreach will stop when one sequence runs out
            Assert.AreEqual(0, lastOne);
            Assert.AreEqual("alpha", lastTwo);
        }

        [Test]
        public void ForEach2Empty()
        {
            var one = Seq.Build<int>();
            var two = Seq.Build<int>();

            one.ForEach2(two, (oneValue, twoValue) => oneValue++);
        }

        [Test]
        public void ZipThisException()
        {
            IEnumerable<int> nullSequence = null;
            var two = 0.Through(4);

            Assert.Throws<ArgumentNullException>(() => nullSequence.Zip(two, (first, second) => first * second));
        }

        [Test]
        public void ZipNullSequenceException()
        {
            var one = 0.Through(4);
            IEnumerable<int> nullSequence = null;

            Assert.Throws<ArgumentNullException>(() => one.Zip(nullSequence, (first, second) => first * second));
        }

        [Test]
        public void ZipNullSelectorException()
        {
            var one = 0.Through(4);
            var two = 0.Through(5);
            Func<int, int, int> selector = null;

            Assert.Throws<ArgumentNullException>(() => one.Zip(two, selector));
        }

        [Test]
        public void Zip3()
        {
            var one = 0.Through(5);
            var two = 2.Through(12, 2);
            var three = 5.Through(10);

            var result = one.Zip(two, three, (x, y, z) => x * y * z);

            var expected = Seq.Build(0, 24, 84, 192, 360, 600);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void Zip3FirstShort()
        {
            var one = 0.Through(4);
            var two = 2.Through(12, 2);
            var three = 5.Through(10);

            var result = one.Zip(two, three, (x, y, z) => x * y * z);

            var expected = Seq.Build(0, 24, 84, 192, 360);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void Zip3SecondShort()
        {
            var one = 0.Through(5);
            var two = 2.Through(10, 2);
            var three = 5.Through(10);

            var result = one.Zip(two, three, (x, y, z) => x * y * z);

            var expected = Seq.Build(0, 24, 84, 192, 360);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void Zip3ThirdShort()
        {
            var one = 0.Through(5);
            var two = 2.Through(12, 2);
            var three = 5.Through(8);

            var result = one.Zip(two, three, (x, y, z) => x * y * z);

            var expected = Seq.Build(0, 24, 84, 192);
            Assert.AreEqual(expected, result);
        }

        [Test]
        public void Zip3ThisException()
        {
            IEnumerable<int> nullSequence = null;
            var two = 2.Through(12, 2);
            var three = 5.Through(8);

            Assert.Throws<ArgumentNullException>(() => nullSequence.Zip(two, three, (x, y, z) => x * y * z));
        }

        [Test]
        public void Zip3SecondException()
        {
            var one = 0.Through(5);
            IEnumerable<int> two = null;
            var three = 5.Through(8);

            Assert.Throws<ArgumentNullException>(() => one.Zip(two, three, (x, y, z) => x * y * z));
        }

        [Test]
        public void Zip3ThirdException()
        {
            var one = 0.Through(5);
            var two = 0.Through(10);
            IEnumerable<int> three = null;

            Assert.Throws<ArgumentNullException>(() => one.Zip(two, three, (x, y, z) => x * y * z));
        }

        [Test]
        public void Zip3SelectorException()
        {
            var one = 0.Through(5);
            var two = 0.Through(10);
            var three = 0.Through(10);
            Func<int, int, int, int> selector = null;

            Assert.Throws<ArgumentNullException>(() => one.Zip(two, three, selector));
        }


        [Test]
        public void ZipToTuplesThisException()
        {
            IEnumerable<int> nullSequence = null;
            var two = 0.Through(4);

            Assert.Throws<ArgumentNullException>(() => nullSequence.Zip(two));
        }

        [Test]
        public void ZipToTuplesNullSecondSequenceException()
        {
            var one = 0.Through(4);
            IEnumerable<int> nullSequence = null;

            Assert.Throws<ArgumentNullException>(() => one.Zip(nullSequence));
        }

        [Test]
        public void EmptySequencesReturnsEmptySequence()
        {
            var sequence1 = new int[0];
            var sequence2 = new int[0];
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(0, result.Count());
        }

        [Test]
        public void SingleSequencereturnsThatSequence()
        {
            var sequence1 = new int[] { 1, 2, 3, 4, 5 };
            var sequence2 = new int[0];
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(sequence1, result);
        }

        [Test]
        public void SinglSecondeSequencereturnsThatSequence()
        {
            var sequence1 = new int[0];
            var sequence2 = new int[] { 1, 2, 3, 4, 5 };
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(sequence2, result);
        }

        [Test]
        public void FirstSequenceLessThanSecondSequenceProducesCorrectMerge()
        {
            var sequence1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            var sequence2 = new int[] { 20, 22, 24, 26, 28, 30 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);

        }

        [Test]
        public void SecondSequenceLessThanFirstSequenceProducesCorrectMerge()
        {
            var sequence1 = new int[] { 100, 101, 102, 103, 104, 105, 106, 107 };
            var sequence2 = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);

        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void NullFirstSeqeunceThrowsArgumentNullException()
        {
            IEnumerable<int> sequence1 = null;
            var sequence2 = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26 };

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);
        }

        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void NullSecondSeqeunceThrowsArgumentNullException()
        {
            var sequence1 = new int[] { 100, 101, 102, 103, 104, 105, 106, 107 };
            IEnumerable<int> sequence2 = null;

            var expected = sequence1.Concat(sequence2).OrderBy(n => n).ToList();
            var result = sequence1.SortedMerge(sequence2);
            Assert.AreEqual(expected, result);
        }

        #endregion

    }
}
